﻿using System;
using System.Security.Cryptography;
namespace Kyoh.Lib.IO
{
    /// <summary>CRC-32による <see cref="HashAlgorithm"/> の実装です。</summary>
	public class Crc32 : HashAlgorithm
	{
        /// <summary>CRC算出に用いるデフォルトの多項式値です。</summary>
		[CLSCompliant(false)]
		public const UInt32 DefaultPolynomial = 0xedb88320;
        /// <summary>CRC算出に用いるデフォルトのシード値です。</summary>
        [CLSCompliant(false)]
		public const UInt32 DefaultSeed = 0xffffffff;

		private UInt32 hash;
		private UInt32 seed;
		private UInt32[] table;

        /// <summary><see cref="Crc32"/> クラスの新しいオブジェクトを作成します。</summary>
		public Crc32()
		{
			table = InitializeTable(DefaultPolynomial);
			seed = DefaultSeed;
			Initialize();
		}

        /// <summary>多項式値とシード値を指定して <see cref="Crc32"/> クラスの新しいオブジェクトを作成します。</summary>
        /// <param name="polynomial">CRC算出に使用する32bit多項式値。</param>
        /// <param name="seed">CRC算出に使用する32bitシード値。</param>
		[CLSCompliant(false)]
		public Crc32(UInt32 polynomial, UInt32 seed)
		{
			table = InitializeTable(polynomial);
			this.seed = seed;
			Initialize();
		}

        /// <summary><see cref="Crc32"/> クラスの実装を初期化します。</summary>
		public override void Initialize()
		{
			hash = seed;
		}

        /// <summary>オブジェクトに書き込まれたデータにハッシュ アルゴリズムを適用します。</summary>
        /// <param name="array">ハッシュ コードを計算する対象の入力データ。</param>
        /// <param name="ibStart">バイト配列内のデータの使用開始位置を示すオフセット。</param>
        /// <param name="cbSize">バイト配列内でデータとして使用されるバイトの数。</param>
		protected override void HashCore(byte[] array, int ibStart, int cbSize)
		{
			hash = CalculateHash(table, hash, array, ibStart, cbSize);
		}

        /// <summary>暗号ストリーム オブジェクトによって最後のデータを処理した後に、ハッシュ計算を終了します。</summary>
        /// <returns>算出したハッシュコードを表す <see cref="Byte"/> 配列。</returns>
		protected override byte[] HashFinal()
		{
			byte[] hashBuffer = UInt32ToBigEndianBytes(hash);
			this.HashValue = hashBuffer;
			return hashBuffer;
		}

        /// <summary>ハッシュ コードのサイズをビット単位で取得します。</summary>
		public override int HashSize
		{
			get { return 32; }
		}

        /// <summary>多項式値とシード値を指定して <see cref="Crc32"/> クラスによるハッシュ計算を行います。</summary>
        /// <param name="polynomial">CRC算出に使用する32bit多項式値。</param>
        /// <param name="seed">CRC算出に使用する32bitシード値。</param>
        /// <param name="buffer">ハッシュ コードを計算する対象の入力データ。</param>
        /// <returns>算出したハッシュコードを表す <see cref="System.UInt32"/> 値。</returns>
		[CLSCompliant(false)]
		public static UInt32 Compute(UInt32 polynomial, UInt32 seed, byte[] buffer)
		{
			return CalculateHash(InitializeTable(polynomial), seed, buffer, 0, buffer.Length);
		}

		private static UInt32[] InitializeTable(UInt32 polynomial)
		{
			UInt32[] createTable = new UInt32[256];
			for (int i = 0; i < 256; i++)
			{
				UInt32 entry = (UInt32)i;
				for (int j = 0; j < 8; j++)
					if ((entry & 1) == 1)
						entry = (entry >> 1) ^ polynomial;
					else
						entry = entry >> 1;
				createTable[i] = entry;
			}
			return createTable;
		}

		private static UInt32 CalculateHash(UInt32[] table, UInt32 seed, byte[] buffer, int start, int size)
		{
			UInt32 crc = seed;
			for (int i = start; i < size; i++)
				unchecked
				{
					crc = (crc >> 8) ^ table[buffer[i] ^ crc & 0xff];
				}
			return ~crc;
		}

		private static byte[] UInt32ToBigEndianBytes(UInt32 x)
		{
			return new byte[] {
			(byte)((x >> 24) & 0xff),
			(byte)((x >> 16) & 0xff),
			(byte)((x >> 8) & 0xff),
			(byte)(x & 0xff)
		};
		}
	}
}